package com.lucifer.cloud.boot.blog.service.impl;
import com.alibaba.nacos.shaded.com.google.common.collect.Lists;
import com.alibaba.nacos.shaded.com.google.common.collect.Maps;
import com.lucifer.cloud.boot.blog.chat.tools.DateTimeTools;
import com.lucifer.cloud.boot.blog.domin.dto.ai.ChatDto;
import com.lucifer.cloud.boot.blog.service.AiService;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.reader.ExtractedTextFormatter;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author lucifer
 * @date 2024/11/5 15:54
 */
@Slf4j
@Service
public class AiServiceImpl implements AiService {

    @Value("${custom.ocr.dataPath}")
    private String dataPath;
    /**
     * -1表示识别失败
     */
    private static final int RLT_INVALID = -1;
    /**
     * 模型文件的位置
     */
    private String modelPath = "D:\\data\\deeplearning4j\\dataset\\mnist_png\\model.zip";
    /**
     * 处理图片文件的目录
     */
    private String imageFilePath = "D:\\data\\deeplearning4j\\dataset\\mnist_png\\images\\";
    private static final String CHAT_HISTORY_KEY = "chat_history_";

    // 用户历史消息列表集
    private Map<String, List<Message>> historyMessagesMap = Maps.newHashMap();

    // 历史消息列表的最大长度
    private static int maxLen = 10;

    // 系统提示词
    private final static String SYSTEM_PROMPT = """
            你需要使用文档内容对用户提出的问题进行回复，同时你需要表现得天生就知道这些内容，
            不能在回复中体现出你是根据给出的文档内容进行回复的，这点非常重要。
 
            当用户提出的问题无法根据文档内容进行回复或者你也不清楚时，回复不知道即可。
 
            文档内容如下:
            {documents}
 
            """;

    /**
     * 神经网络
     */
    //private MultiLayerNetwork net;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private ZhiPuAiChatModel zhiPuAiChatModel;

    @Resource
    private OpenAiChatModel openAiChatModel;


    @Resource
    private OllamaChatModel ollamaChatModel;

    @Resource(name = "pgVectorStore")
    private VectorStore vectorStore;


    @Resource
    private TokenTextSplitter tokenTextSplitter;


    /**
     * bean实例化成功就加载模型
     */
//    @PostConstruct
//    private void loadModel() {
//        log.info("load model from [{}]", modelPath);
//        // 加载模型
//        try {
//            net = ModelSerializer.restoreMultiLayerNetwork(new File(modelPath));
//            log.info("module summary\n{}", net.summary());
//        } catch (Exception exception) {
//            log.error("loadModel error", exception);
//        }
//    }


    @Override
    public void vecAdd() {
        List<Document> documents = List.of(
                new Document("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!", Map.of("meta1", "meta1")),
                new Document("The World is Big and Salvation Lurks Around the Corner"),
                new Document("You walk forward facing the past and you turn back toward the future.", Map.of("meta2", "meta2")));
        vectorStore.add(documents);
    }

    @Override
    public List<Document> vecSearch(String query) {
        List<Document> results = this.vectorStore.similaritySearch(SearchRequest.builder().query(query).topK(3).build());
        return results;
    }

    @Override
    @SneakyThrows
    public void saveFile(MultipartFile file) {
        // 获取文件名
        String fileName = file.getOriginalFilename();
        // 获取文件内容类型
        String contentType = file.getContentType();
        // 获取文件字节数组
        byte[] bytes = file.getBytes();
        // 创建一个临时文件
        Path tempFile = Files.createTempFile("temp-", fileName);
        // 将文件字节数组保存到临时文件
        Files.write(tempFile, bytes);
        // 创建一个 FileSystemResource 对象
        org.springframework.core.io.Resource fileResource = new FileSystemResource(tempFile.toFile());
        PdfDocumentReaderConfig loadConfig = PdfDocumentReaderConfig.builder()
                .withPageExtractedTextFormatter(
                        new ExtractedTextFormatter
                                .Builder()
                                .withNumberOfBottomTextLinesToDelete(3)
                                .withNumberOfTopPagesToSkipBeforeDelete(1)
                                .build()
                )
                .withPagesPerDocument(1)
                .build();

        PagePdfDocumentReader pagePdfDocumentReader = new PagePdfDocumentReader(fileResource, loadConfig);
        vectorStore.accept(tokenTextSplitter.apply(pagePdfDocumentReader.get()));
    }

    @Override
    public String query(String message) {
        // 根据问题文本进行相似性搜索
        List<Document> listOfSimilarDocuments = vectorStore.similaritySearch(message);
        // 将Document列表中每个元素的content内容进行拼接获得documents
        String documents = listOfSimilarDocuments.stream().map(Document::getText).collect(Collectors.joining());
        // 使用Spring AI 提供的模板方式构建SystemMessage对象
        Message systemMessage = new SystemPromptTemplate(SYSTEM_PROMPT).createMessage(Map.of("documents", documents));
        // 构建UserMessage对象
        UserMessage userMessage = new UserMessage(message);
        // 将Message列表一并发送给ChatGPT
//        ChatResponse rsp = openAiChatModel.call(new Prompt(List.of(systemMessage, userMessage)));
//        return rsp.getResult().getOutput().getText();

        ChatClient.CallResponseSpec call = ChatClient.create(openAiChatModel)
                .prompt(new Prompt(List.of(systemMessage, userMessage)))
                .tools(new DateTimeTools())
                .call();
        return call.chatResponse().getResult().getOutput().getText();


    }

    @Override
    public String dsChat(String sessionId, String message) {
        List<Message> historyMessages = historyMessagesMap.get(sessionId);
        if (null == historyMessages) {
            historyMessages = Lists.newArrayList();
        }
        historyMessages.add(new UserMessage(message));
        if (historyMessages.size() > maxLen) {
            historyMessages = historyMessages.subList(historyMessages.size() - maxLen - 1, historyMessages.size());
        }
        ChatResponse chatResponse = openAiChatModel.call(new Prompt(historyMessages));
        AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
        // 将AI回复的消息放到历史消息列表中
        String assistantMessageText = assistantMessage.getText();
        //historyMessages.add(assistantMessage);
        //historyMessages.add(new UserMessage(assistantMessageText));
        historyMessagesMap.put(sessionId, historyMessages);
        return assistantMessageText;
    }

    @Override
    public ChatDto zhiPuChat(Map map) {
        List<Map> messages = (List) map.get("messages");
        Map mp = messages.get(0);
        String content = (String) mp.get("content");
        String sessionId = (String) mp.get("sessionId");
        String keyword = CHAT_HISTORY_KEY + sessionId;
        ListOperations<String, String> listOps = redisTemplate.opsForList();
        List<String> history = listOps.range(keyword, 0, -1);

        List<Message> mess = Lists.newArrayList();
        for (String historicalMessage : history) {
            UserMessage message = new UserMessage(historicalMessage);
            mess.add(message);
        }
        UserMessage me = new UserMessage(content);
        mess.add(me);
        Prompt prompt = new Prompt(mess);

        ChatResponse chatResponse = zhiPuAiChatModel.call(prompt);
        String textContent = chatResponse.getResult().getOutput().getText();

        List<ChatDto.Choice> choices = Lists.newArrayList();

        ChatDto.Choice.Delta delta = ChatDto.Choice.Delta.builder().content(textContent).build();
        ChatDto.Choice choice = ChatDto.Choice.builder().delta(delta).build();
        choices.add(choice);
        ChatDto dto = ChatDto.builder().choices(choices).build();
        if (redisTemplate.opsForList().size(keyword) >= 6) {
            listOps.leftPop(keyword);
        }
        listOps.rightPush(keyword, "user:" + content); //指定role角色更有利于ai理解上下文信息
        listOps.rightPush(keyword, "assistant:" + chatResponse);

        return dto;
    }


//    @Override
//    @SneakyThrows
//    public int predict(MultipartFile file, boolean isNeedRevert) {
//        log.info("start predict, file [{}], isNeedRevert [{}]", file.getOriginalFilename(), isNeedRevert);
//
//        // 先存文件
//        String rawFileName = ImageFileUtil.save(imageFilePath, file);
//
//        if (null == rawFileName) {
//            return RLT_INVALID;
//        }
//
//        // 反色处理后的文件名
//        String revertFileName = null;
//
//        // 调整大小后的文件名
//        String resizeFileName;
//
//        // 是否需要反色处理
//        if (isNeedRevert) {
//            // 把原始文件做反色处理，返回结果是反色处理后的新文件
//            revertFileName = ImageFileUtil.colorRevert(imageFilePath, rawFileName);
//
//            // 把反色处理后调整为28*28大小的文件
//            resizeFileName = ImageFileUtil.resize(imageFilePath, revertFileName);
//        } else {
//            // 直接把原始文件调整为28*28大小的文件
//            resizeFileName = ImageFileUtil.resize(imageFilePath, rawFileName);
//        }
//
//        // 现在已经得到了结果反色和调整大小处理过后的文件，
//        // 那么原始文件和反色处理过的文件就可以删除了
//        ImageFileUtil.clear(imageFilePath, rawFileName, revertFileName);
//
//        // 取出该黑白图片的特征
//        INDArray features = ImageFileUtil.getGrayImageFeatures(imageFilePath, resizeFileName);
//
//        // 将特征传给模型去识别
//        return net.predict(features)[0];
//    }
//
//    static Map<@Nullable Integer, @Nullable String> map;
//
//    static {
//        map = Maps.newHashMap();
//        map.put(0, "apple");
//        map.put(1, "cat");
//        map.put(2, "dog");
//        map.put(3, "durk");
//    }
//
//    @SneakyThrows
//    @Override
//    public String predict(MultipartFile file) {
//        File file2File = multipartFile2File(file);
//        INDArray indArray = PredictImage.loadImage(file2File);
//        int[] predict = net.predict(indArray);
//        String modelPrediction = map.get(predict[0]);
//        return modelPrediction;
//    }
//
//    @Override
//    public String orc(MultipartFile file) {
//        String result = null;
//        File imageFile = new File("tempImage.png");
//        try {
//            File fe = multipartFile2File(file);
//            boolean pdf = file.getOriginalFilename().endsWith(".pdf");
//            Tesseract tesseract = new Tesseract();
//            tesseract.setDatapath(dataPath);
//            tesseract.setLanguage("chi_sim"); // 设置语言
//            if (pdf) {
//                File[] files = PdfUtilities.convertPdf2Png(fe);
//                imageFile = files[0];
//                result = tesseract.doOCR(imageFile);
//            }
//            result = tesseract.doOCR(fe);
//        } catch (Exception e) {
//            e.printStackTrace();
//        } finally {
//            imageFile.delete();
//        }
//        return result;
//    }
//
//
//    private File multipartFile2File(MultipartFile multiFile) {
//        String fileName = multiFile.getOriginalFilename();
//        String prefix = fileName.substring(fileName.lastIndexOf("."));
//        try {
//            File file = File.createTempFile(fileName, prefix);
//            multiFile.transferTo(file);
//            return file;
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//        return null;
//    }
}
